package com.ehi.common.jersey.status;

import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotAcceptableException;
import javax.ws.rs.NotAllowedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.util.Enumeration;
import java.util.UUID;

@Provider
public class DefaultExceptionMapper implements ExceptionMapper<Exception> {

    public static final String KEY_JVM_ID = "ehi.jvm.id";
    private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionMapper.class);
    @Context
    private HttpServletRequest request;

    @Override
    public Response toResponse(Exception exception) {
        ExceptionMessage exceptionMessage = createExceptionMessage(exception);
        if (exception instanceof IllegalArgumentException) {
            logWarning(exceptionMessage);
            return ResponseBuilder.badRequest(exception.getMessage());
        }

        if (exception instanceof NotFoundException) {
            logWarning(exceptionMessage);
            return createStatusMessageResponse(Status.NOT_FOUND, ResponseTypeCode.NOT_FOUND, "Resource doesn't exist");
        }

        if (exception instanceof NotAllowedException) {
            logWarning(exceptionMessage);
            return createStatusMessageResponse(Status.METHOD_NOT_ALLOWED, ResponseTypeCode.METHOD_NOT_ALLOWED, "Please check the HTTP METHOD.");
        }

        if (exception instanceof NotSupportedException) {
            logWarning(exceptionMessage);
            return createStatusMessageResponse(Status.UNSUPPORTED_MEDIA_TYPE, ResponseTypeCode.UNSUPPORTED_MEDIA_TYPE, "Please check the HTTP content-type header.");
        }

        if (exception instanceof NotAcceptableException) {
            logWarning(exceptionMessage);
            return createStatusMessageResponse(Status.NOT_ACCEPTABLE, ResponseTypeCode.NOT_ACCEPTABLE, "Please check the HTTP accept header.");
        }

        if (exception instanceof ForbiddenException) {
            logWarning(exceptionMessage);
            return createStatusMessageResponse(Status.FORBIDDEN, ResponseTypeCode.FORBIDDEN, "Access forbidden.");
        }

        logException(exception, exceptionMessage);

        return ResponseBuilder.serverError(exceptionMessage);
    }

    private Response createStatusMessageResponse(Status status,
                                                 ResponseTypeCode responseTypeCode, String message) {
        return Response.status(status).entity(ErrorMessageWrapper.buildErrorMessage(responseTypeCode, message)).type(MediaType.APPLICATION_JSON).build();
    }

    private void logWarning(ExceptionMessage exceptionMessage) {
        String message = createExceptionLog(exceptionMessage);
        LOG.warn(message);
    }

    private void logException(Exception exception,
                              ExceptionMessage exceptionMessage) {
        String message = createExceptionLog(exceptionMessage);
        LOG.error(message, exception);
    }

    private String createExceptionLog(ExceptionMessage exceptionMessage) {
        StringBuilder message = new StringBuilder();
        message.append(String.format("=================== Exception Id: %s START ===================\n", exceptionMessage.getExceptionId()));
        if (request != null) {
            message.append(String.format("Request URI: %s\n", request.getPathInfo()));
            message.append(String.format("Request query String: %s\n", request.getQueryString()));

            Enumeration<?> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = (String) headerNames.nextElement();
                message.append(String.format("Request header: %s:%s\n", headerName, request.getHeader(headerName)));
            }
            Cookie[] cookies = request.getCookies();
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    message.append(String.format("Request cookie: %s:%s\n", cookie.getName(), cookie.getValue()));
                }
            }
            Enumeration<?> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String parameterName = (String) parameterNames.nextElement();
                message.append(String.format("Request param: %s:%s\n", parameterName, request.getParameter(parameterName)));
            }
        }
        message.append(String.format("An exception occurred, message is: %s\n", exceptionMessage.getMessage()));
        message.append(String.format("=================== Exception Id: %s END ===================\n", exceptionMessage.getExceptionId()));
        return message.toString();
    }

    @SuppressWarnings("unused")
    private String getJvmId() {
        String jvmId = System.getProperty(KEY_JVM_ID);
        if (Strings.isNullOrEmpty(jvmId)) {
            LOG.info("Unknown jvm id.\nPerhaps -D{}=<value> not set on java command line?", KEY_JVM_ID);
            jvmId = "";
        }
        return jvmId;
    }

    private ExceptionMessage createExceptionMessage(Exception exception) {
        ExceptionMessage exceptionMessage = new ExceptionMessage();
        String message = createErrorMessage(exception);
        exceptionMessage.setMessage(message);
        String exceptionId = UUID.randomUUID().toString();
        exceptionMessage.setExceptionId(exceptionId);
        return exceptionMessage;
    }

    private String createErrorMessage(Exception exception) {
        String message = exception.getMessage();
        if (Strings.isNullOrEmpty(message)) {
            message = exception.getClass().getSimpleName();
        }

        return message;
    }

}
